/*
 * Copyright (C) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <filesystem>

#include "SkFontMgr_ohos.h"

#include "include/core/SkRefCnt.h"
#include "include/private/base/SkDebug.h"
#include "modules/skparagraph/include/TextStyle.h"
#include <native_drawing/drawing_typeface.h>
#include <native_drawing/drawing_memory_stream.h>

/*! Constructor
 * \param path the full path of system font configuration document
 */
SkFontMgr_OHOS::SkFontMgr_OHOS(const char* path)
{
    fFontMgrOHOS = OH_Drawing_FontMgrCreate();
}

SkFontMgr_OHOS::~SkFontMgr_OHOS()
{
    if (fFontMgrOHOS) {
        OH_Drawing_FontMgrDestroy(fFontMgrOHOS);
        fFontMgrOHOS = nullptr;
    }
}
class SkFontStyleSet_OHOS : public SkFontStyleSet {
public:
    SkFontStyleSet_OHOS(OH_Drawing_FontMgr *mgr, int index) {
        fFontStyleSet = OH_Drawing_FontMgrCreateFontStyleSet(mgr, index);
    }
    SkFontStyleSet_OHOS(OH_Drawing_FontStyleSet *styleSet) {
        assert(styleSet != nullptr);
        fFontStyleSet = styleSet;
    }
    ~SkFontStyleSet_OHOS() override {
        if (fFontStyleSet) {
            OH_Drawing_FontMgrDestroyFontStyleSet(fFontStyleSet);
        }
    }
    int count() override { return OH_Drawing_FontStyleSetCount(fFontStyleSet); }
    void getStyle(int index, SkFontStyle* fontStyle, SkString* style) override {
        char* retStyleName = nullptr;
        auto retStyle = OH_Drawing_FontStyleSetGetStyle(fFontStyleSet, index, &retStyleName);
        if (retStyleName && style) {
            style->set(retStyleName);
            OH_Drawing_FontStyleSetFreeStyleName(&retStyleName);
        }
        if (fontStyle) {
            *fontStyle = SkFontStyle(skia::textlayout::TextStyle::toSkFontWeight(retStyle.weight),
                                     retStyle.width,
                                     (SkFontStyle::Slant)retStyle.slant);
        }
    }
    sk_sp<SkTypeface> createTypeface(int index) override {
        auto retTypeface = OH_Drawing_FontStyleSetCreateTypeface(fFontStyleSet, index);
        SkString fontName;
        SkFontStyle fontStyle;
        getStyle(index, &fontStyle, &fontName);
        return sk_sp<SkTypeface>(new SkTypeface(retTypeface, fontName.c_str(), fontStyle));
    }
    sk_sp<SkTypeface> matchStyle(const SkFontStyle& pattern) override {
        // NOTE: use OH_Drawing_FontStyleSetMatchStyle cannot get styleName and fontStyle efficiently.
//        auto retTypeface = OH_Drawing_FontStyleSetMatchStyle(
//                fFontStyleSet, skia::textlayout::TextStyle::toOHFontStyle(pattern));
//        return sk_sp<SkTypeface>(new SkTypeface(retTypeface, pattern));
        return this->matchStyleCSS3(pattern); 
    }

private:
    OH_Drawing_FontStyleSet *fFontStyleSet = nullptr;
};
/*! To get the count of families
 * \return The count of families in the system
 */
int SkFontMgr_OHOS::onCountFamilies() const
{
    return OH_Drawing_FontMgrGetFamilyCount(fFontMgrOHOS);
}

/*! To get the family name for a font style set
 * \param index the index of a font style set
 * \param[out] familyName the family name returned to the caller
 * \n          The family name will be reset to "", if index is out of range
 */
void SkFontMgr_OHOS::onGetFamilyName(int index, SkString* familyName) const
{
    if (!familyName) {
        return;
    }
    auto str = OH_Drawing_FontMgrGetFamilyName(fFontMgrOHOS, index);
    familyName->set(str);
}

/*! To create an object of SkFontStyleSet
 * \param index the index of a font style set
 * \return The pointer of SkFontStyleSet
 * \n      Return null, if index is out of range
 * \note   The caller must call unref() on the returned object if it's not null
 */
sk_sp<SkFontStyleSet> SkFontMgr_OHOS::onCreateStyleSet(int index) const
{
    auto styleSet = OH_Drawing_FontMgrCreateFontStyleSet(fFontMgrOHOS, index);
    return sk_sp<SkFontStyleSet>(new SkFontStyleSet_OHOS(styleSet));
}

/*! To get a matched object of SkFontStyleSet
 * \param familyName the family name of a font style set
 * \return The pointer of SkFontStyleSet
 * \n      Return the default font style set, if family name is null
 * \n      Return null, if family name is not found
 * \note   The caller must call unref() on the returned object if it's not null
 */
sk_sp<SkFontStyleSet> SkFontMgr_OHOS::onMatchFamily(const char familyName[]) const
{
    auto styleSet = OH_Drawing_FontMgrMatchFamily(fFontMgrOHOS, familyName);
    return sk_sp<SkFontStyleSet>(new SkFontStyleSet_OHOS(styleSet));
//     if (fontConfig == nullptr) {
//         SkDEBUGF("onMatchFamily get null fontConfig");
//         return nullptr;
//     }
//     SkDEBUGF("onMatchFamily get familyName %s", familyName);
//     // return default system font when familyName is null
//     if (familyName == nullptr) {
//         SkDEBUGF("onMatchFamily get null familyName");
//         return sk_ref_sp(new SkFontStyleSet_OHOS(fontConfig, 0));
//     }
//
//     bool isFallback = false;
//     int index = fontConfig->getStyleIndex(familyName, isFallback);
//     if (index == -1) {
//         SkDEBUGF("onMatchFamily get -1 index");
//         return nullptr;
//     }
//     SkDEBUGF("onMatchFamily get index %d", index);
//     return sk_ref_sp(new SkFontStyleSet_OHOS(fontConfig, index, isFallback));
}

/*! To get a matched typeface
 * \param familyName the family name of a font style set
 * \param style the font style to be matched
 * \return An object of typeface which is closest matching to 'style'
 * \n      Return the typeface in the default font style set, if family name is null
 * \n      Return null, if family name is not found
 * \note   The caller must call unref() on the returned object if it's not null
 */
sk_sp<SkTypeface> SkFontMgr_OHOS::onMatchFamilyStyle(const char familyName[], const SkFontStyle& style) const
{
    auto typeFace = OH_Drawing_FontMgrMatchFamilyStyle(
            fFontMgrOHOS, familyName, skia::textlayout::TextStyle::toOHFontStyle(style));
    return sk_sp<SkTypeface>(new SkTypeface(typeFace, familyName, style));
    //     if (fontConfig == nullptr) {
    //         return nullptr;
    //     }
    //     bool isFallback = false;
    //     int styleIndex = 0;
    //     if (familyName) {
    //         styleIndex = fontConfig->getStyleIndex(familyName, isFallback);
    //     }
    //     return fontConfig->getTypeface(styleIndex, style, isFallback);
}

/*! To get a matched typeface
 * \n Use the system fallback to find a typeface for the given character.
 * \param familyName the family name which the typeface is fallback For
 * \param style the font style to be matched
 * \param bcp47 an array of languages which indicate the language of 'character'
 * \param bcp47Count the array size of bcp47
 * \param character a UTF8 value to be matched
 * \return An object of typeface which is for the given character
 * \return Return the typeface in the default fallback set, if familyName is null
 * \return Return null, if the typeface is not found for the given character
 * \note The caller must call unref() on the returned object if it's not null
 */
sk_sp<SkTypeface> SkFontMgr_OHOS::onMatchFamilyStyleCharacter(const char familyName[], const SkFontStyle& style,
    const char* bcp47[], int bcp47Count, SkUnichar character) const
{
    auto typeFace = OH_Drawing_FontMgrMatchFamilyStyleCharacter(
            fFontMgrOHOS,
            familyName,
            skia::textlayout::TextStyle::toOHFontStyle(style),
            bcp47,
            bcp47Count,
            character);
    return sk_sp<SkTypeface>(new SkTypeface(typeFace, familyName, style));
    
    //     if (fontConfig == nullptr) {
    //         return nullptr;
    //     }
    //     const FallbackForMap& fallbackForMap = fontConfig->getFallbackForMap();
    //     const FallbackSet& fallbackSet = fontConfig->getFallbackSet();
    //     SkString defaultFamily("");
    //     SkString key = defaultFamily;
    //     FallbackSetPos* item = nullptr;
    //     if (familyName == nullptr) {
    //         item = fallbackForMap.find(defaultFamily);
    //     } else {
    //         item = fallbackForMap.find(SkString(familyName));
    //         if (item) {
    //             key = SkString(familyName);
    //         } else {
    //             item = fallbackForMap.find(defaultFamily);
    //         }
    //     }
    //     if (item == nullptr) {
    //         SkDEBUGF("%s : '%s' must be a fallback key in the config file\n",
    //             FontConfig_OHOS::errToString(ERROR_FAMILY_NOT_FOUND), defaultFamily.c_str());
    //         return nullptr;
    //     }
    //     while (true) {
    //         if (bcp47Count > 0) {
    //             sk_sp<SkTypeface> retTp = findTypeface(*item, style, bcp47, bcp47Count,
    //             character); if (retTp) {
    //                 return retTp;
    //             }
    //             if (key == defaultFamily) {
    //                 bcp47Count = 0;
    //                 continue;
    //             }
    //             item = fallbackForMap.find(defaultFamily);
    //             key = defaultFamily;
    //         } else {
    //             for (unsigned int i = item->index; i < item->index + item->count && i <
    //             fallbackSet.size(); i++) {
    //                 const TypefaceSet& tpSet = *(fallbackSet[i]->typefaceSet.get());
    //                 if (tpSet.size() > 0 && tpSet[0]->unicharToGlyph(character) != 0) {
    //                     sk_sp<SkTypeface> typeface = FontConfig_OHOS::matchFontStyle(tpSet,
    //                     style); return typeface;
    //                 }
    //             }
    //             if (key == defaultFamily) {
    //                 break;
    //             }
    //             item = fallbackForMap.find(defaultFamily);
    //             key = defaultFamily;
    //         }
    //     }
    //     return nullptr;
}

/*! To find the matched typeface for the given parameters
 * \n Use the system fallback to find a typeface for the given character.
 * \param fallbackItem the fallback items in which to find the typeface
 * \param style the font style to be matched
 * \param bcp47 an array of languages which indicate the language of 'character'
 * \param bcp47Count the array size of bcp47
 * \param character a UTF8 value to be matched
 * \return An object of typeface which is for the given character
 * \return Return null, if the typeface is not found for the given character
 */
// sk_sp<SkTypeface> SkFontMgr_OHOS::findTypeface(const FallbackSetPos& fallbackItem, const SkFontStyle& style,
//     const char* bcp47[], int bcp47Count, SkUnichar character) const
// {
//     if (bcp47Count == 0) {
//         return nullptr;
//     }
//
//     const FallbackSet& fallbackSet = fontConfig->getFallbackSet();
//     // example bcp47 code : 'zh-Hans' : ('zh' : iso639 code, 'Hans' : iso15924 code)
//     // iso639 code will be taken from bcp47 code, so that we can try to match
//     // bcp47 or only iso639. Therefore totalCount need to be 'bcp47Count * 2'
//     int totalCount = bcp47Count * 2;
//     int tps[totalCount];
//     for (int i = 0; i < totalCount; i++) {
//         tps[i] = -1;
//     }
//     // find the families matching the bcp47 list
//     for (unsigned int i = fallbackItem.index; i < fallbackItem.index + fallbackItem.count
//         && i < fallbackSet.size(); i++) {
//         int ret = compareLangs(fallbackSet[i]->langs, bcp47, bcp47Count, tps);
//         if (ret == -1) {
//             continue;
//         }
//         tps[ret] = i;
//     }
//     // match typeface in families
//     for (int i = bcp47Count - 1; i >= 0; i--) {
//         if (tps[i] == -1) {
//             continue;
//         }
//         const TypefaceSet& tpSet = *(fallbackSet[tps[i]]->typefaceSet.get());
//         if (tpSet.size() > 0 && tpSet[0]->unicharToGlyph(character) != 0) {
//             sk_sp<SkTypeface> typeface = FontConfig_OHOS::matchFontStyle(tpSet, style);
//             return typeface;
//         }
//     }
//     for (int i = totalCount - 1; i >= bcp47Count; i--) {
//         if (tps[i] == -1) {
//             continue;
//         }
//         const TypefaceSet& tpSet = *(fallbackSet[tps[i]]->typefaceSet.get());
//         if (tpSet.size() > 0 && tpSet[0]->unicharToGlyph(character) != 0) {
//             sk_sp<SkTypeface> typeface = FontConfig_OHOS::matchFontStyle(tpSet, style);
//             return typeface;
//         }
//     }
//     return nullptr;
// }

/*! To compare the languages of an typeface with a bcp47 list
 * \param langs the supported languages by an typeface
 * \param bcp47 the array of bcp47 language to be matching
 * \param bcp47Count the array size of bcp47
 * \param tps an array of the index of typeface which is matching one value of bcp47
 * \return The index of language in bcp47, if matching happens
 * \n      Return -1, if no language matching happens
 */
// int SkFontMgr_OHOS::compareLangs(const SkString& langs, const char* bcp47[],
//     int bcp47Count, const int tps[]) const
// {
//     /*
//      * zh-Hans : ('zh' : iso639 code, 'Hans' : iso15924 code)
//      */
//     if (bcp47 == nullptr || bcp47Count == 0) {
//         return -1;
//     }
//     for (int i = bcp47Count - 1; i >= 0; i--) {
//         if (tps[i] != -1) {
//             continue;
//         }
//         if (langs.find(bcp47[i]) != -1) {
//             return i;
//         } else {
//             const char* iso15924 = strrchr(bcp47[i], '-');
//             if (iso15924 == nullptr) {
//                 continue;
//             }
//             iso15924++;
//             int len = iso15924 - 1 - bcp47[i];
//             SkString country(bcp47[i], len);
//             if (langs.find(iso15924) != -1 ||
//                 (strncmp(bcp47[i], "und", strlen("und")) && langs.find(country.c_str()) != -1)) {
//                 return i + bcp47Count;
//             }
//         }
//     }
//     return -1;
// }


/*! To create a typeface from the specified data and TTC index
 * \param data the data to be parsed
 * \param index the index of typeface. 0 for none
 * \return The object of typeface, if successful
 * \n      Return null if the data is not recognized.
 * \note The caller must call unref() on the returned object if it's not null
 */
sk_sp<SkTypeface> SkFontMgr_OHOS::onMakeFromData(sk_sp<SkData> data, int ttcIndex) const
{
    if (data == nullptr) {
        return nullptr;
    }
    // !NOTE: this name cannot work by set font family, this is a fake font name 
    SkString fontName = skia::textlayout::FontCollection::getNextFontName();
    
    sk_sp<SkTypeface> ret = nullptr;
    auto ohMS = OH_Drawing_MemoryStreamCreate(data->data(), data->size(), false);
    // ohMS will be auto released when typeface is released.
    auto typeface = OH_Drawing_TypefaceCreateFromStream(ohMS, ttcIndex);
    if (typeface) {
        ret = sk_sp<SkTypeface>(new SkTypeface(typeface, fontName.c_str(), data));
    }
    return ret;

//     std::unique_ptr<SkMemoryStream> memoryStream = std::make_unique<SkMemoryStream>(data);
//     SkFontArguments args;
//     args.setCollectionIndex(ttcIndex);
//     return this->makeTypeface(std::move(memoryStream), args, nullptr);
}

/*! To create a typeface from the specified stream and TTC index
 * \param data the stream to be parsed
 * \param index the index of typeface. 0 for none
 * \return The object of typeface, if successful
 * \n      Return null if the stream is not recognized.
 * \note The caller must call unref() on the returned object if it's not null
 */
sk_sp<SkTypeface> SkFontMgr_OHOS::onMakeFromStreamIndex(std::unique_ptr<SkStreamAsset> stream,
    int ttcIndex) const
{
    // OH_Drawing_Typeface * OH_Drawing_TypefaceCreateFromFile (const char *path, int index)
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     if (stream == nullptr) {
//         return nullptr;
//     }
//     SkFontArguments args;
//     args.setCollectionIndex(ttcIndex);
//     return this->makeTypeface(std::move(stream), args, nullptr);
}

/*! To create a typeface from the specified stream and font arguments
 * \param data the stream to be parsed
 * \param args the arguments of font
 * \return The object of typeface, if successful
 * \n      Return null if the stream is not recognized.
 * \note The caller must call unref() on the returned object if it's not null
 */
sk_sp<SkTypeface> SkFontMgr_OHOS::onMakeFromStreamArgs(std::unique_ptr<SkStreamAsset> stream,
    const SkFontArguments& args) const
{
    RENDER_UNIMPLEMENTED;
    return nullptr;
//     if (stream == nullptr) {
//         return nullptr;
//     }
//
//     return this->makeTypeface(std::move(stream), args, nullptr);
}

/*! To create a typeface from the specified font file and TTC index
 * \param path the full path of the given font file
 * \param ttcIndex the index of typeface in a ttc font file. 0 means none.
 * \return The object of typeface, if successful
 * \n      Return null if the font file is not found or the content of file is not recognized.
 * \note The caller must call unref() on the returned object if it's not null
 */
sk_sp<SkTypeface> SkFontMgr_OHOS::onMakeFromFile(const char path[], int ttcIndex) const
{
//    auto typeface = OH_Drawing_TypefaceCreateFromFile(path, ttcIndex);
//    // @todo how to get familyName from OH typeface or font file?
//    return sk_sp<SkTypeface>(new SkTypeface(typeface));
    std::filesystem::path systemPath(path);
    SkString fontName(systemPath.stem().string());// get file name as Font FamilyName

    sk_sp<SkTypeface> ret = nullptr;
    auto typeface = OH_Drawing_TypefaceCreateFromFile(path, ttcIndex);
    if (typeface) {
        ret = sk_sp<SkTypeface>(new SkTypeface(typeface, fontName.c_str()));
    }
    return ret;
//     if (fontConfig == nullptr) {
//         return nullptr;
//     }
//
//     std::unique_ptr<SkStreamAsset> stream = SkStreamAsset::MakeFromFile(path);
//     if (stream == nullptr) {
//         SkDEBUGF("%s : %s\n", FontConfig_OHOS::errToString(ERROR_FONT_NOT_EXIST), path);
//         return nullptr;
//     }
//     SkFontArguments args;
//     args.setCollectionIndex(ttcIndex);
//     return this->makeTypeface(std::move(stream), args, path);
}

/*! To get a typeface matching the specified family and style
 * \param familyName the specified name to be matching
 * \param style the specified style to be matching
 * \return The object of typeface which is the closest matching 'style' when the familyName is found
 * \return Return a typeface from the default family, if familyName is not found
 * \return Return null, if there is no any typeface in the system
 * \note The caller must caller unref() on the returned object is it's not null
 */
sk_sp<SkTypeface> SkFontMgr_OHOS::onLegacyMakeTypeface(const char familyName[], SkFontStyle style) const
{
    sk_sp<SkTypeface> typeface = this->onMatchFamilyStyle(familyName, style);
    // if familyName is not found, then try the default family
    if (typeface == nullptr && familyName != nullptr) {
        typeface = this->onMatchFamilyStyle(nullptr, style);
    }
    return typeface;
//
//    if (typeface) {
//        return sk_sp<SkTypeface>(typeface);
//    }
    SkDEBUGF("onLegacyMakeTypeface ERROR_NO_AVAILABLE_FAMILY\n");
    return nullptr;
}

/*! To make a typeface from the specified stream and font arguments
 * \param stream the specified stream to be parsed to get font information
 * \param args the arguments of index or axis values
 * \param path the fullname of font file
 * \return The object of typeface if successful
 * \n      Return null, if the stream is not recognized
 */
// sk_sp<SkTypeface> SkFontMgr_OHOS::makeTypeface(std::unique_ptr<SkStreamAsset> stream,
//     const SkFontArguments& args, const char path[]) const
// {
//     FontInfo fontInfo;
//     int ttcIndex = args.getCollectionIndex();
//     int axisCount = args.getVariationDesignPosition().coordinateCount;
//
//     if (path) {
//         fontInfo.fname.set(path);
//     }
//     if (axisCount == 0) {
//         if (!fontScanner.scanInstance(stream.get(), ttcIndex, 0, &fontInfo.familyName, &fontInfo.style,
//             &fontInfo.isFixedWidth, nullptr)) {
//             SkDEBUGF("%s\n", FontConfig_OHOS::errToString(ERROR_FONT_INVALID_STREAM));
//             return nullptr;
//         }
//     } else {
//         AxisDefinitions axisDef;
//         if (!fontScanner.scanInstance(stream.get(), ttcIndex, 0, &fontInfo.familyName, &fontInfo.style,
//             &fontInfo.isFixedWidth, &axisDef)) {
//             SkDEBUGF("%s\n", FontConfig_OHOS::errToString(ERROR_FONT_INVALID_STREAM));
//             return nullptr;
//         }
//         if (axisDef.size() > 0) {
//             SkFixed axis[axisCount];
//             fontScanner.computeAxisValues(axisDef, args.getVariationDesignPosition(),
//                 axis, fontInfo.familyName);
//             fontInfo.setAxisSet(axisCount, axis, axisDef.data());
//         }
//     }
//
//     fontInfo.stream = std::move(stream);
//     fontInfo.index = ttcIndex;
//     return sk_make_sp<SkTypeface_OHOS>(fontInfo);
// }

/*! To create SkFontMgr object for Harmony platform
 * \param fname the full name of system font configuration documents
 * \return The object of SkFontMgr_OHOS
 */
sk_sp<SkFontMgr> SkFontMgr_New_OHOS(const char* fname)
{
return sk_make_sp<SkFontMgr_OHOS>(fname);
}

sk_sp<SkFontMgr> SkFontMgr_New_OHOS() {
    return SkFontMgr_New_OHOS("/system/etc/fontconfig.json");
}

// make RefDefault to singleton ohos fontMgr
sk_sp<SkFontMgr> SkFontMgr::RefDefault() {
    static SkOnce once;
    static sk_sp<SkFontMgr> singleton;

    once([] {
        singleton = SkFontMgr_New_OHOS();
    });

    return singleton;
}
